/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package DataMining;

import Common.KeyValue;
import DataSource.Data;
import DataSource.Subscriber;
import java.util.Vector;

/**
 *
 * @author Tuan
 */
public class ClusteringImpl implements Clustering, Subscriber {

    private Vector points;
    private Vector currentPoints;
    private Vector listeners;
    private Vector clusters;
    private Point centroid1;
    private Point centroid2;
    private Point centroid3;
    private boolean isRunning;
    private Vector dataItems;
    private static final Object lockObj = new Object();

    public ClusteringImpl() {
        this.points = new Vector();
        this.currentPoints = new Vector();
        this.dataItems = new Vector();
        this.listeners = new Vector();
    }

    public synchronized void registerCallback(ClusteringListener listener) {
        this.listeners.addElement(listener);
    }

    private void onMiningDone() {
        Cluster[] arrClusters = new Cluster[this.clusters.size()];
        this.clusters.copyInto(arrClusters);
        for (int i = 0; i < this.listeners.size(); i++) {
            ClusteringListener listener = (ClusteringListener) this.listeners.elementAt(i);
            listener.onMiningDone(arrClusters);
        }
    }

    /*
     * Initialize the clusters and centroids
     */
    public void startMining() {
        synchronized (lockObj) {
            if (isRunning) {
                return;
            }
            isRunning = true;
        }
        try {
            synchronized (lockObj) {
                for (int i = 0; i < this.currentPoints.size(); i++) {
                    this.points.addElement((Point) this.currentPoints.elementAt(i));
                }
            }
            clusters = new Vector(3);

            //Create new cluster, assign 3 first elements as centroid points
            Cluster cluster1 = new Cluster((Point) this.points.elementAt(0));
            Cluster cluster2 = new Cluster((Point) this.points.elementAt(1));
            Cluster cluster3 = new Cluster((Point) this.points.elementAt(2));
            clusters.addElement(cluster1);
            clusters.addElement(cluster2);
            clusters.addElement(cluster3);

            //Create new centroids
            this.centroid1 = ((Cluster) this.clusters.elementAt(0)).getCentroid();
            this.centroid2 = ((Cluster) this.clusters.elementAt(1)).getCentroid();
            this.centroid3 = ((Cluster) this.clusters.elementAt(2)).getCentroid();

            //start calcucalting the distance from points from centroids
            calculateDistance(3);
        } finally {
            isRunning = false;
        }
    }

    /*
     * Calculate the distance from point to centroids
     * k is the index of cluster in clusters vector
     * DataItem:
     * Index: index of cluster
     * Object: Point
     */
    private void calculateDistance(int index) {
        this.dataItems = new Vector();
        for (int i = index; i < this.points.size(); i++) {
            //get the point
            Point point = (Point) this.points.elementAt(i);
            Cluster firstCluster = (Cluster) this.clusters.elementAt(0);
            Point firstCenPoint = firstCluster.getCentroid();
            double smallest = firstCenPoint.calculateDistance(point);            
            int k = 0;
            for (int j = 0; j < this.clusters.size(); j++) {
                Cluster cluster = (Cluster) this.clusters.elementAt(j);
                Point cenPoint = cluster.getCentroid();
                double distance = cenPoint.calculateDistance(point);
                if (distance < smallest) {                    
                    k = j;
                    smallest = distance;                
                }
            }
            KeyValue dataItem = new KeyValue(k, point);
            dataItems.addElement(dataItem);
        }
        //relocate points to clusters
        relocation();

    }

    private void relocation() {
        //assign points to clusters
        for (int i = 0; i < this.dataItems.size(); i++) {
            KeyValue dataItem = (KeyValue) this.dataItems.elementAt(i);
            ((Cluster) this.clusters.elementAt(dataItem.Index)).assignNewPoint((Point) dataItem.Value);
        }
        //calculate new centroid
        Cluster cl1 = ((Cluster) this.clusters.elementAt(0));
        Cluster cl2 = ((Cluster) this.clusters.elementAt(1));
        Cluster cl3 = ((Cluster) this.clusters.elementAt(2));
        cl1.reCalculateCentroid();
        cl2.reCalculateCentroid();
        cl3.reCalculateCentroid();       
        //Check if centroid are equals to previous centroid
        if (this.centroid1.equals(cl1.getCentroid()) && this.centroid2.equals(cl2.getCentroid()) && this.centroid3.equals(cl3.getCentroid())) {
            this.onMiningDone();
            this.points.removeAllElements();
        } else {
            //assign centroids to previous centroids
            
            this.centroid1 = cl1.getCentroid();
            cl1.removeAllPoints();
            
            this.centroid2 = cl2.getCentroid();
            cl2.removeAllPoints();
            
            this.centroid3 = cl3.getCentroid();
            cl3.removeAllPoints();
            
            //remove all points and calculate distance to add new points again
            calculateDistance(0);
        }
    }

    public void update(Data data) {
        synchronized (lockObj) {
            Point point = new Point(data.getSystolic(), data.getDiastolic(), data.getHeartBeat());
            this.currentPoints.addElement(point);
        }
    }
    
    public String getReport(){
        StringBuffer builder = new StringBuffer();
        builder.append("Clusters have been calculated").append("\n");
        for(int i = 0; i < this.clusters.size(); i ++){
            Cluster cluster = (Cluster) this.clusters.elementAt(i);
            Point centroid = cluster.getCentroid();
            builder.append("Centroid ").append(i).append(": ").append(centroid.toString()).append("\n");
        }
        builder.append("\n");
        builder.append("Details of clusters\n");
        for(int i = 0; i < this.clusters.size(); i ++){
            Cluster cluster = (Cluster) this.clusters.elementAt(i);
            Point centroid = cluster.getCentroid();
            Vector points = cluster.getAllPoints();
            builder.append("Cluster ").append(i + 1).append("\n");
            builder.append("Centroid: ").append(centroid.toString()).append("\n");
            builder.append("List of points (").append(points.size()).append(")\n");
            for(int j = 0; j < points.size(); j++){
                Point p = (Point) points.elementAt(j);
                builder.append(p.toString()).append("\n");
            }
            builder.append("~~~~~~~~~~~~~~~~~~\n");
        }
        return builder.toString();
    }
}
